/**
 *  \file I3OverlayNetwork_Layer.cpp
 *  \brief This file contains the definition of methods of the I3OverlayNetwork 
 *  class that are mandated by the OCD API.
 */

#include "i3_debug.h"
#include "OCD_include.h"

#include "I3OverlayNetwork.h"
#include "I3Functor.h"
#include "I3AddressBook.h"
#include "I3Id.h"
#include "I3Key.h"
#include "PublicTrigger.h"
#include "I3OCDStateInfo.h"
#include "I3Params.h"
#include "I3Packet.h"
#include "I3TimerEvent.h"
#include "I3ClientContext.h"
#include "i3Debug.h"

#include "I3Trigger.h"

#include <assert.h>

extern "C" {
    #include "i3.h"
    #include "i3_api.h"
    #include "i3_id.h"
    #include "i3_addr.h"
    #include "i3_trigger.h"
    #include "i3_client_api.h"
}

/**
  * Setup up a tunnel over i3 to the specified name.
  * If a tunnel already exists, reuse it.
  * @param ociHandle The oci to be notified through a callback when the tunnel setup succeeds
  * or fails.
  * @parentLayer The layer which is setting up this new layer.
  * @return a handle to the tunnel.
  */
LayerHandle I3OverlayNetwork::setup (
                            const string endPointName, 
                            int endPointNameType,
                            char *optionBlocks, 
                            int optionLen, 
                            LayerHandle ociHandle,
                            OCI* oci) {
   
    if (endPointNameType == OCI_NAME) {
       
        // Check if there already exists a tunnel to the end point
        I3OCDStateInfo *i3State = (I3OCDStateInfo *) lookupEndPoint (endPointName);
        LayerHandle i3Handle = NULL_HANDLE;
        
        if (i3State == NULL || 
		i3State->state == FAKE_STATE_NEW /*recovering from crash, tunnel already appears to be there, but not set up */) {
           // No tunnel exists,  create a new one.
 
	    if (i3State == NULL) {
	            i3Handle = getNewOCDHandle();
        	    i3State = new I3OCDStateInfo (i3Handle, oci, this);
	            i3State->i3VNType =  I3_VN_TYPE_LOCAL_CREATED;
            	    //add to the list of ocd states
                    addOCDState (i3State);

	    } else {
		    i3Handle = i3State->myHandle;
            }

            I3_PRINT_DEBUG2 (
                    DEBUG_LEVEL_SUPER, 
                    "In I3OverlayNetwork setup, creating a new tunnel handle %d, endpointName  = %s\n", 
                    i3Handle,
                    i3State->endPointName
                );

            // Add the specified ociHandle to the list of OCIStates waiting for this OCD setup to complete
            i3State->addParentHandle (ociHandle);

            putStateInfo (i3Handle, i3State);

            // add newly created state to hash table keyed by end point name.
            putEndPoint  (endPointName, i3State);

            strcpy (i3State->endPointName, endPointName.c_str());

            i3State->setupOptionBlocks = optionBlocks;
            i3State->setupOptionLen = optionLen;

            // If the overlay is set to do fast connection startups, so is this new VN
            i3State->enableFastConnectionSetup = enableFastConnectionStartup;
    
        
            ID* lid = i3AddressBook->lookup_addrbook(i3State->endPointName);
            i3State->remotePublicId = new I3Id();
    
            if ( lid != NULL ) {
                init_i3_id(&i3State->remotePublicId->id, lid);
                free_i3_id(lid);
        
            } else if ( enableHashedURLs ) {
                I3Id::hash_URL_on_ID(
                        i3State->endPointName,
                        &i3State->remotePublicId->id
                        );
                I3_PRINT_DEBUG1 (
                        I3_DEBUG_LEVEL_VERBOSE,
                        "The requested name %s was not found in your address book.\n"
                        "So the i3 id will be obtained by taking a hash on the name.\n",
                        i3State->endPointName
                );
                i3State->nameHashedFlag = true;
            } else {
        
                I3_PRINT_INFO1(
                    I3_INFO_LEVEL_WARNING,
                    "Public trigger for %s not specified in address book"//
                    "and enableHashedURLs option not set.  Unable to accept the request.\n", 
                    i3State->endPointName
                );
    
                i3State->notifySetupFailure();
                return NULL_HANDLE;
            }
            i3State->remotePublicIdStack.ids = &i3State->remotePublicId->id;
            i3State->remotePublicIdStack.len = 1;
    
            i3State->addr_type = I3_ADDR_TYPE_IPv4;
    
            initFakeState(i3State, FAKE_STATE_NEW, NULL);
        
            if (i3State->enableFastConnectionSetup) {
                //we are not even waiting for the private trigger insertion
                //THe application can straight away start sending data.
                //The data will be sent to the remote host at its public trigger.
                i3State->notifySetupSuccess();
            }
    
        } else {
    
            I3_PRINT_DEBUG2 (
                    DEBUG_LEVEL_SUPER,
                    "Tunnel to hop end point %s already exists with handle %d.\n"
                    "We will reuse the tunnel after refreshing it.\n",
                    i3State->endPointName,
                    i3State->myHandle
                );
    
            // Tunnel to end point already exists.  Reuse it.
            // Make sure that the tunnel is alive
            
            i3Handle = i3State->myHandle;
    
            // We are just refreshing the tunnel; making sure that it
            // is alive.
            i3State->state = FAKE_STATE_RENEW;
            requestPrivateTrigger(i3State);
            
            // Add the specified ociHandle to the list of OCIStates waiting for this OCD setup to complete
            i3State->addParentHandle (ociHandle);
 
           
            i3State->addr_type = I3_ADDR_TYPE_IPv4;
    
            /*
            initFakeState(i3State, FAKE_STATE_NEW);
        
            if (i3State->enableFastConnectionSetup) {
                //we are not even waiting for the private trigger insertion
                //THe application can straight away start sending data.
                //The data will be sent to the remote host at its public trigger.
                i3State->notifySetupSuccess();
            }
            */
    
        }
        
        return i3Handle;
    
    } else {
    
        // The endPointName specified in the setup call is an i3 id or i3 stack.
        // There is no need for resolution.
        
        // Check if there already exists a tunnel to the end point
        I3OCDStateInfo *i3State = (I3OCDStateInfo *) lookupEndPointId (endPointName);
        LayerHandle i3Handle = NULL_HANDLE;
        
        if (i3State == NULL) {
            // No tunnel exists,  create a new one.
            i3Handle = getNewOCDHandle();
            i3State = new I3OCDStateInfo (i3Handle, oci, this);
    
            // Add the specified ociHandle to the list of OCIStates waiting for this OCD setup to complete
            i3State->addParentHandle (ociHandle);

            putStateInfo (i3Handle, i3State);

            // add newly created state to hash table keyed by end point name.
            putEndPointId  (endPointName, i3State);

            StringTokenizer st = StringTokenizer (endPointName, ",");
            i3State->remotePublicIdStack.len = st.countTokens();
            i3State->remotePublicIdStack.ids = (ID *) malloc ( i3State->remotePublicIdStack.len * sizeof (ID));

            int stackIndex = 0;
            while (st.hasMoreTokens()) {

                string tmpStr = st.nextToken();
                I3_PRINT_DEBUG1 (
                                DEBUG_LEVEL_SUPER,
                                "Setting remote public id stack : %s.\n",
                                tmpStr.c_str()
                            );
                I3Id::convertStr2I3Id( tmpStr.c_str(), &i3State->remotePublicIdStack.ids[stackIndex]);
                stackIndex ++;
                
            }
 
            //TODO We need not set remotePublicId.. But just because it is painful to make the check for remotePublicId
            // in all places, set it to the first id of the remotePublicIdStack.
            i3State->remotePublicId = new I3Id();
            memcpy ( &(i3State->remotePublicId->id), &i3State->remotePublicIdStack.ids[0], sizeof (ID));

            
            strcpy (i3State->endPointName, endPointName.c_str());

            i3State->addr_type = I3_ADDR_TYPE_IPv4;

            initFakeState(i3State, FAKE_STATE_NEW, NULL);

            return i3Handle;
       
        } else {
             I3_PRINT_DEBUG2 (
                    DEBUG_LEVEL_SUPER,
                    "Tunnel to OCD specified hop end point %s already exists with handle %d.\n"
                    "We will reuse the tunnel after refreshing it.\n",
                    i3State->endPointName,
                    i3State->myHandle
                );
    
            // Tunnel to end point already exists.  Reuse it.
            // Make sure that the tunnel is alive
            i3Handle = i3State->myHandle;
    
            // We are just refreshing the tunnel; making sure that it
            // is alive.
            i3State->state = FAKE_STATE_RENEW;
            requestPrivateTrigger(i3State);
            
            // Add the specified ociHandle to the list of OCIStates waiting for this OCD setup to complete
            i3State->addParentHandle (ociHandle);
 
            i3State->addr_type = I3_ADDR_TYPE_IPv4;

            return i3Handle;
        }
    }
}
    
/**
  * This function is called when some other layer wants to send anything via this overlay.
  */
void I3OverlayNetwork::send ( LayerHandle handle, char *data, int dataLen) {

    I3OCDStateInfo *ocdState = (I3OCDStateInfo *) getStateInfo (handle);

    if (ocdState == NULL) {
        I3_PRINT_DEBUG1 (
                        DEBUG_LEVEL_MINIMAL,
                        "Trying to send through unknown OCD with handle %d\n",
                        handle
                    );
        return;
    } 
    
    static cl_buf *clb = NULL;
    if (NULL == clb) {
        clb =  cl_alloc_buf (MAX_PACKET_BUF_LEN);
    }
    
    I3_PRINT_DEBUG2 (DEBUG_LEVEL_VERBOSE, "I3 Sending packet of len %d from VN with %s.\n", dataLen, sprintf_i3_id (tmpBuf, &ocdState->prv_id ));

    if (ocdState->state == FAKE_STATE_OK ||
            (ocdState->enableFastConnectionSetup)) {
                
        packPacket (clb, data, dataLen, ocdState); 
        
        if (
                ocdState->state == FAKE_STATE_OK 
                        ||
                ( (ocdState->state == FAKE_STATE_CNF || ocdState->state == FAKE_STATE_RECNF) && ocdState->enableFastConnectionSetup)) {

            // Send to the remote private trigger   
            I3_PRINT_DEBUG1 ( DEBUG_LEVEL_VERBOSE,
                    "Sending data to private id %s\n",
                    sprintf_i3_id (tmpBuf, ocdState->remotePrivateIdStack.ids)
                );
            context->sendToStack(&ocdState->remotePrivateIdStack, clb, enableShortcut?CL_PKT_FLAG_ALLOW_SHORTCUT:0);
        
        } else {
            // Send to the remote public trigger.
            I3_PRINT_DEBUG1 ( DEBUG_LEVEL_VERBOSE,
                    "Sending data to public id %s\n",
                    sprintf_i3_id (tmpBuf, ocdState->remotePublicIdStack.ids)
                );
            context->sendToStack(&ocdState->remotePublicIdStack, clb, enableShortcut?CL_PKT_FLAG_ALLOW_SHORTCUT:0);
        }
  
        //TODO
        //ocdState->setActive();

    } else {
        //TODO handle other states
        I3_PRINT_DEBUG0 (DEBUG_LEVEL_MINIMAL, "Received data packet when I3 VN was not in OK state.\n");
    }
}

/**
  * Does the I3 overlay want to enforce the next hop?
  */
string I3OverlayNetwork::getNextHopId (LayerHandle handle) {

    I3OCDStateInfo* ocdState = (I3OCDStateInfo *) getStateInfo (handle);

    if ( ocdState != NULL) {

        string retStr = "";

        for (int i = 0; i < ocdState->triggerStackLen; i++) {

            char tmpStr[MAX_PRINT_BUF];
            sprintf_i3_id (tmpStr, &(ocdState->triggerStack[i]));
            retStr += tmpStr;
            retStr += ",";
        }

        return retStr;

    } else {

        I3_PRINT_DEBUG1 (
                        DEBUG_LEVEL_SUPER,
                        "Unknown i3 handle %d while trying to I3 level next hop id.\n",
                        handle
                    );
        return "";

    }
}

string I3OverlayNetwork::getMiddleBoxName (LayerHandle handle) {

    I3OCDStateInfo *i3State = (I3OCDStateInfo *) getStateInfo (handle);
    assert (i3State != NULL);

    // return the name associated with id on which the private trigger request was received.
    return getNameById (&i3State->contactId);
    
}

/**
  * Initializes the state necessary for inbound names.
  * In the case of i3, triggers are inserted.
  */
void I3OverlayNetwork::initMyNames () {

    // Insert any triggers specified without DNS name, i.e. directly as id point to IP address.
    insertTriggers();

}


#ifdef _WIN32 
void I3OverlayNetwork::sleepProxy() {
	context->closeAllSockets();
}
#endif

// Register the OCD with the proxy when the code is dynamically loaded
// The following line sets the name of the OCD's main class
#define MY_OCD_CLASS_NAME I3OverlayNetwork
// The following line sets the type name of the OCD
#define MY_OCD_TYPE_STR "I3"

#include "OCDRegister.h"
